package com.bestcem.xm.member.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.sequence.dao.SequenceDao;
import com.bestcem.xm.common.core.sequence.entity.SequenceDO;
import com.bestcem.xm.common.core.sequence.entity.SequenceEntity;
import com.bestcem.xm.common.core.uitls.DateUtil;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.member.enums.MemberTypeEnum;
import com.bestcem.xm.member.service.SequenceService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import static com.bestcem.xm.member.constant.Constants.LABEL_DEFAULT_NAME_KEY;
import static com.bestcem.xm.member.constant.Constants.LABEL_GROUP_DEFAULT_NAME_KEY;

/**
 * sequence service
 *
 * @author yf.wang <yf.wang@idiaoyan.com>
 * @version V1.0
 * @date 2021/7/16
 */

@Slf4j
@Service
public class SequenceServiceImpl implements SequenceService {

    private final static String SEQ_LEFT_PREFIX = "_SEQleft_";

    private final static String SEQ_PREFIX = "_SEQ_";

    private final static String TAG = "tag";

    private final static String TAG_GROUP = "tag_group";

    @Resource
    private RedisService redisService;

    @Resource
    private SequenceDao sequenceDao;


    @Override
    public ServiceResult<Integer> getNameSequence(String name, String orgId) {
        String key = null;
        if (TAG.equals(name)) {
            key = LABEL_DEFAULT_NAME_KEY + orgId + "_" + MemberTypeEnum.INDIVIDUAL.getIndex();
        } else if (TAG_GROUP.equals(name)) {
            key = LABEL_GROUP_DEFAULT_NAME_KEY + orgId + "_" + MemberTypeEnum.INDIVIDUAL.getIndex();
        } else {
            return ServiceResult.fail("参数错误");
        }
        return getSequence(key, 1);
    }

    /**
     * 根据 key 和 incrBy 获取序号
     *
     * @param code
     * @param incrBy
     * @return
     */
    @Override
    public ServiceResult<Integer> getSequence(String code, long incrBy) {
        // 判断步长
        if (incrBy <= 0) {
            return ServiceResult.fail("inc_by must greater than zero");
        }

        String leftKey = SEQ_LEFT_PREFIX + code;
        String seqKey = SEQ_PREFIX + code;

        RLock lock = redisService.getLock(code);
        try {
            lock.lock();
            // 查询还未放出的序号的数量
            long leftKeySeq = redisService.decr(leftKey, incrBy);
            Boolean dbIncr = false;
            if (leftKeySeq < 0) {
                // leftKey已经减到0以下或者之前不存在, 不存在的时候可能需要创建记录
                dbIncr = getDbSequence(code, -1 * leftKeySeq);
            }

            // 直接从redis中获取seq自增后的结果
            int seq = redisService.incr(seqKey, incrBy).intValue();
            if (seq == incrBy) {
                // 第一次或key丢失
                // 从db中获取该序号的信息
                SequenceDO sequence = sequenceDao.findOneByCode(code);
                if (sequence.getSeq() == sequence.getIncrStep() - leftKeySeq) {
                    // 若db里的序号 == 设置的自增 - 剩余的还未释放的序号的数量
                    // 数据库的序号自增 + 1
                    sequenceDao.findOneAndUpdateByCodeAndSeqInc(code, 1);
                } else {
                    // 缓存丢失
                    int lastDbSeq;
                    if (dbIncr) {
                        lastDbSeq = sequence.getSeq() - sequence.getIncrStep();
                    } else {
                        sequenceDao.findOneAndUpdateByCodeAndSeqInc(code, sequence.getIncrStep());
                        lastDbSeq = sequence.getSeq();
                    }
                    int incrSeq = lastDbSeq - seq;
                    seq = redisService.incr(seqKey, incrSeq).intValue();
                    if (seq == incrSeq) {

                    } else {
                        sequenceDao.findOneAndUpdateByCodeAndSeqInc(code, seq - lastDbSeq);
                    }
                }
            }
            return ServiceResult.success(Long.valueOf(seq).intValue());
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 从db中获取一段seq，并记录在缓存中，同时从缓存中取出当前序列值返回
     *
     * @param code
     * @param incrBy
     * @return
     */
    private Boolean getDbSequence(String code, long incrBy) {
        // 从db中获取该序号的信息
        SequenceDO sequence = sequenceDao.findOneByCode(code);
        if (ObjectUtil.isEmpty(sequence) || sequence.getSeq() < 0) {
            //创建一个新的记录
            int initSeq = 0;
            sequence = createSequence(code);
        }
        Integer seq = sequence.getSeq();
        int incrStep = sequence.getIncrStep();
        incrStep += incrBy;
        String leftKey = SEQ_LEFT_PREFIX + code;
        // 根据 code 与 seq 更新 seq
        sequenceDao.findOneAndUpdateByCodeAndSeq(code, seq, incrStep);
        // 更新redis
        redisService.incr(leftKey, incrStep);
        return true;
    }

    private SequenceDO createSequence(String code) {
        SequenceEntity sequence = new SequenceEntity();
        sequence.setCode(code);
        sequence.setIncrStep(1000);
        sequence.setSeq(0);
        sequence.setCreateTime(DateUtil.getCommonDate());
        sequence.setUpdateTime(sequence.getCreateTime());
        return sequenceDao.findOneAndUpdateByCode(sequence);
    }
}
